/* http.c : dgnsc http header parser, sip/rtsp can also use
 * Copyright (C) 2009-2011 drangon <drangon.zhou@gmail.com>
 * 2011-09
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/http.h>
#include <dgnsc/hashkv.h>
#include <dgnsc/common.h>

#include <stdlib.h>
#include <string.h>

struct dgn_http_st {
	dgn_str_t str1;
	dgn_str_t str2;
	dgn_str_t str3;

	dgn_hashkv_t header;
};

static unsigned int head_casehash( const void * key )
{
	const char * str = (const char *)key;
	unsigned int hash = 5381;
	int c;
	while( (c = *str++) != 0 ) {
		if( c >= 'a' && c <= 'z' )
			c = c + 'A' - 'a';
		hash = ( (hash << 5) + hash ) + c; // hash * 33 + c
	}
	return hash;
}

static int head_casecmp( const void * key1, const void * key2 )
{
#ifdef _WIN32
	return _stricmp( (const char *)key1, (const char *)key2 );
#else
	return strcasecmp( (const char *)key1, (const char *)key2 );
#endif
}

dgn_http_t dgn_http_init()
{
	struct dgn_http_st * ht = (struct dgn_http_st *)malloc( sizeof(struct dgn_http_st) );
	ht->str1 = dgn_str_init( 0 );
	ht->str2 = dgn_str_init( 0 );
	ht->str3 = dgn_str_init( 0 );
	ht->header = dgn_hashkv_init( head_casehash, head_casecmp,
		   	dgn_hashkv_default_free, dgn_hashkv_default_free );
	return ht;
}

// request
const char * dgn_http_get_method( dgn_http_t ht )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_str_c( ht->str1 );
}

const char * dgn_http_get_uri( dgn_http_t ht )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_str_c( ht->str2 );
}

const char * dgn_http_get_request_ver( dgn_http_t ht )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_str_c( ht->str3 );
}

// response
const char * dgn_http_get_response_ver( dgn_http_t ht )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_str_c( ht->str1 );
}

int dgn_http_get_code( dgn_http_t ht )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return 0;
	}
	const char * p = dgn_str_c( ht->str2 );
	if( p == NULL )
		return 0;
	return atoi( p );
}

const char * dgn_http_get_reason( dgn_http_t ht )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_str_c( ht->str3 );
}

// general header
const char * dgn_http_get_header( dgn_http_t ht, const char * key )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return (const char *)dgn_hashkv_get( ht->header, key );
}

// set value
int dgn_http_set_request( dgn_http_t ht, const char * method, const char * uri, const char * ver )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return -1;
	}
	dgn_str_assign_c( ht->str1, method, -1 );
	dgn_str_assign_c( ht->str2, uri, -1 );
	dgn_str_assign_c( ht->str3, ver, -1 );
	return 0;
}

int dgn_http_set_response( dgn_http_t ht, const char * ver, int code, const char * reason )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return -1;
	}
	dgn_str_assign_c( ht->str1, ver, -1 );
	dgn_str_assign_fmt( ht->str2, "%d", code );
	dgn_str_assign_c( ht->str3, reason, -1 );
	return 0;
}

int dgn_http_set_header( dgn_http_t ht, const char * key, const char * value )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return -1;
	}
	char * nkey = strdup( key );
	char * nval = strdup( value );
	return dgn_hashkv_add( ht->header, nkey, nval );
}

int dgn_http_del_header( dgn_http_t ht, const char * key )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return -1;
	}
	return dgn_hashkv_del( ht->header, key );
}

dgn_http_iter_t dgn_http_iter_next( dgn_http_t ht, dgn_http_iter_t iter )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_hashkv_iter_next( ht->header, iter );
}

dgn_http_iter_t dgn_http_iter_find( dgn_http_t ht, const char * key )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_hashkv_iter_find( ht->header, key );
}

const char * dgn_http_iter_get_key( dgn_http_t ht, dgn_http_iter_t iter )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_hashkv_iter_get_key( ht->header, iter );
}

const char * dgn_http_iter_get( dgn_http_t ht, dgn_http_iter_t iter )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_hashkv_iter_get( ht->header, iter );
}

int dgn_http_iter_set( dgn_http_t ht, dgn_http_iter_t iter, const char * value )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return -1;
	}
	char * nval = strdup( value );
	return dgn_hashkv_iter_set( ht->header, iter, nval, NULL );
}

dgn_http_iter_t dgn_http_iter_del( dgn_http_t ht, dgn_http_iter_t iter )
{
	if( ht == NULL ) {
		PR_DEBUG( "ht is NULL" );
		return NULL;
	}
	return dgn_hashkv_iter_del( ht->header, iter, NULL, NULL );
}

dgn_http_t dgn_http_init_parse( const char * str, int * used_len )
{
	if( used_len != NULL )
		*used_len = 0;
	if( str == NULL )
		return NULL;
	if( strstr( str, "\r\n\r\n" ) == NULL )
		return NULL;

	dgn_http_t ht = dgn_http_init();
	const char * p = NULL;
	int len = 0;

	int slen;
	p = strchr( str + len, ' ' );
	if( p == NULL )
		goto err_quit;
	slen = p - (str + len);
	dgn_str_assign_c( ht->str1, str + len, slen );
	len += (slen + 1);

	p = strchr( str + len, ' ' );
	if( p == NULL )
		goto err_quit;
	slen = p - (str + len);
	dgn_str_assign_c( ht->str2, str + len, slen );
	len += (slen + 1);

	p = strstr( str + len, "\r\n" );
	if( p == NULL )
		goto err_quit;
	slen = p - (str + len);
	dgn_str_assign_c( ht->str3, str + len, slen );
	len += (slen + 2);

	while( str[len] != '\r' || str[len + 1] != '\n' ) {
		p = strchr( str + len, ':' );
		if( p == NULL )
			goto err_quit;
		slen = p - (str + len);
		char * key = (char *)malloc( slen + 1 );
		memcpy( key, str + len, slen );
		key[slen] = '\0';
		len += (slen + 1);

		// skip leading LWS
		while( str[len] == ' ' || str[len] == '\t' )
			len += 1;

		p = NULL;
		slen = 0;
		while( 1 ) {
			p = strstr( str + len + slen, "\r\n" );
			if( p[2] == ' ' || p[2] == '\t' ) {
				slen = p - (str + len) + 2;
				continue;
			}
			else {
				slen = p - (str + len);
				break;
			}
		}

		// skip end LWS
		int tmp = 0;
		while( tmp < slen && (str[len + slen - tmp - 1] == ' ' || str[len + slen - tmp - 1] == '\t' ) )
				tmp += 1;

		char * val = (char *)malloc( slen - tmp + 1 );
		memcpy( val, str + len, slen - tmp );
		val[slen - tmp] = '\0';
		len += (slen + 2);
		dgn_hashkv_add( ht->header, key, val );
	}
	len += 2;

	*used_len = len;
	return ht;

err_quit:
	*used_len = len;
	dgn_http_fini( ht );
	return NULL;
}

int dgn_http_output( dgn_http_t ht, dgn_str_t buf )
{
	if( ht == NULL || buf == NULL ) {
		PR_DEBUG( "ht or buf is NULL" );
		return -1;
	}

	dgn_str_append_fmt( buf, "%s %s %s\r\n", dgn_str_c(ht->str1), dgn_str_c(ht->str2), dgn_str_c(ht->str3) );
	dgn_hashkv_iter_t iter;
	for( iter = dgn_hashkv_iter_next( ht->header, NULL ); iter != NULL; iter = dgn_hashkv_iter_next( ht->header, iter ) ) {
		dgn_str_append_fmt( buf, "%s: %s\r\n", 
				(const char *)dgn_hashkv_iter_get_key(ht->header, iter), 
				(const char *)dgn_hashkv_iter_get(ht->header, iter) );
	}
	dgn_str_append_c( buf, "\r\n", -1 );
	return 0;
}

void dgn_http_fini( dgn_http_t ht )
{
	if( ht == NULL )
		return;
	dgn_str_fini( ht->str1 );
	dgn_str_fini( ht->str2 );
	dgn_str_fini( ht->str3 );
	dgn_hashkv_fini( ht->header );
	free( ht );
	return;
}

